package com.hanshg.cherry.config.appender;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.filter.ThresholdFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.helpers.Transform;
import com.hanshg.cherry.model.SysErrorLog;
import com.hanshg.cherry.service.log.ErrorLogService;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.Date;

/**
 * @ClassName DbErrorLogAppender
 * @Description TODO
 * @Author 柠檬水
 * @Date 2020/4/21 21:14
 * @Version 1.0
 **/
@Configuration
public class DbErrorLogAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    private final ErrorLogService errorLogService;

    public DbErrorLogAppender(ErrorLogService errorLogService) {
        this.errorLogService = errorLogService;
    }

    /**
     * DbErrorLogAppender初始化
     */
    @PostConstruct
    public void init() {
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        ThresholdFilter filter = new ThresholdFilter();
        filter.setLevel("ERROR");
        filter.setContext(context);
        filter.start();
        this.addFilter(filter);
        this.setContext(context);
        context.getLogger("ROOT").addAppender(DbErrorLogAppender.this);
        super.start();
    }

    @Override
    protected void append(ILoggingEvent loggingEvent) {
        IThrowableProxy tp = loggingEvent.getThrowableProxy();
        // ErrorLogPO数据表实体类
        SysErrorLog errorLog = new SysErrorLog();
        errorLog.setErrMsg(loggingEvent.getMessage());
        errorLog.setCreateTime(new Date(loggingEvent.getTimeStamp()));
        if (loggingEvent.getCallerData() != null && loggingEvent.getCallerData().length > 0) {
            StackTraceElement element = loggingEvent.getCallerData()[0];
            errorLog.setClassName(element.getClassName());
            errorLog.setMethodName(element.getMethodName());
        }

        if (tp != null) {
            errorLog.setExceptionName(tp.getClassName());
            errorLog.setStackTrace(getStackTraceMsg(tp));
        }
        try {
            // 错误日志实体类写入数据库
            errorLogService.save(errorLog);
        } catch (Exception ex) {
            this.addError("上报错误日志失败：" + ex.getMessage());
        }
    }


    /**
     * 拼装堆栈跟踪信息
     */
    private String getStackTraceMsg(IThrowableProxy tp) {
        StringBuilder buf = new StringBuilder();
        if (tp != null) {
            while (tp != null) {
                this.renderStackTrace(buf, tp);
                tp = tp.getCause();
            }
        }
        return buf.toString();
    }

    /**
     * 堆栈跟踪信息拼装成html字符串
     */
    private void renderStackTrace(StringBuilder sbuf, IThrowableProxy tp) {
        this.printFirstLine(sbuf, tp);
        int commonFrames = tp.getCommonFrames();
        StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
        for (int i = 0; i < stepArray.length - commonFrames; ++i) {
            StackTraceElementProxy step = stepArray[i];
            sbuf.append("<br />&nbsp;&nbsp;&nbsp;&nbsp;");
            sbuf.append(Transform.escapeTags(step.toString()));
            sbuf.append(CoreConstants.LINE_SEPARATOR);
        }
        if (commonFrames > 0) {
            sbuf.append("<br />&nbsp;&nbsp;&nbsp;&nbsp;");
            sbuf.append("\t... ").append(commonFrames).append(" common frames omitted").append(CoreConstants.LINE_SEPARATOR);
        }

    }

    /**
     * 拼装堆栈跟踪信息第一行
     */
    public void printFirstLine(StringBuilder sb, IThrowableProxy tp) {
        int commonFrames = tp.getCommonFrames();
        if (commonFrames > 0) {
            sb.append("<br />").append("Caused by: ");
        }
        sb.append(tp.getClassName()).append(": ").append(Transform.escapeTags(tp.getMessage()));
        sb.append(CoreConstants.LINE_SEPARATOR);
    }
}
